/*
*  crypto.c - Crypto Functions
*  Copyright (c) 2005 Derek Ingrouville (dergro@users.sourceforge.net)
*
*  This program is free software; you can redistribute it and/or modify
*  it under the terms of the GNU General Public License as published by
*  the Free Software Foundation; either version 2 of the License, or
*  (at your option) any later version.
*
*  This program is distributed in the hope that it will be useful,
*  but WITHOUT ANY WARRANTY; without even the implied warranty of
*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
*  GNU Library General Public License for more details.
*
*  You should have received a copy of the GNU General Public License
*  along with this program; if not, write to the Free Software
*  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/

#include "crypto.h"

#include <stdio.h>
#include <string.h>
#include <glib.h>

Digest *
digest_new (DigestType type)
{
	Digest *digest = NULL;
	const EVP_MD *md;

	switch (type) {
		case DIGEST_MD5: {
			md = EVP_md5 ();
			break;
		}
		case DIGEST_SHA1: {
			md = EVP_sha1 ();
			break;
		}
		default: {
			g_message ("Unknown message digest %i\n", type);
		}
	}

	if (md == NULL)
		return NULL;

	digest = g_new (Digest, 1);

	EVP_MD_CTX_init(&digest->mdctx);
	EVP_DigestInit_ex(&digest->mdctx, md, NULL);
	digest->md = md;

	return digest;
}

void
digest_append (Digest *digest, const void *buf, size_t size)
{
	g_return_if_fail (digest != NULL);
	g_return_if_fail (digest->md != NULL);
	g_return_if_fail (buf != NULL);
	g_return_if_fail (size > 0);

	EVP_DigestUpdate(&digest->mdctx, buf, size);
}

void
digest_finish (Digest *digest, unsigned char *buf, unsigned int *size)
{
	g_return_if_fail (digest != NULL);
	g_return_if_fail (digest->md != NULL);

	EVP_DigestFinal_ex (&digest->mdctx, buf, size);
}

char *
digest_final_str (Digest *digest)
{
	char *str = NULL;
	unsigned char md_value [EVP_MAX_MD_SIZE];
	unsigned char tmp_buf [EVP_MAX_MD_SIZE];
	int len = 0, i = 0;

	g_return_val_if_fail (digest != NULL, NULL);
	g_return_val_if_fail (digest->md != NULL, NULL);

	digest_finish (digest, md_value, &len);

	for (i = 0; i < 16; i++)
		g_snprintf (tmp_buf + (i * 2), 3, "%02x", md_value[i]);

	str = g_strdup (tmp_buf);

	return str;
}

void
digest_free (Digest *digest)
{
	g_return_if_fail (digest != NULL);
	g_return_if_fail (digest->md != NULL);

	EVP_MD_CTX_cleanup(&digest->mdctx);
	digest->md = NULL;

	g_free (digest);
}

char *
md5_hash_str (const char *format, ...)
{
	Digest *digest = NULL;
	va_list args;
	char *str = NULL;
	char *hash = NULL;

	g_return_val_if_fail (format != NULL, NULL);

	va_start (args, format);
	str = g_strdup_vprintf (format, args);
	va_end (args);

	if (str == NULL)
		return NULL;

	digest = digest_new (DIGEST_MD5);
	if (digest == NULL) {
		g_free (str);
		return NULL;
	}

	digest_append (digest, str, strlen (str));
	hash = digest_final_str (digest);
	digest_free (digest);

	return hash;
}

char *
sha1_hash_str (const char *format, ...)
{
	Digest *digest = NULL;
	va_list args;
	char *str = NULL;
	char *hash = NULL;

	g_return_val_if_fail (format != NULL, NULL);

	va_start (args, format);
	str = g_strdup_vprintf (format, args);
	va_end (args);

	if (str == NULL)
		return NULL;

	digest = digest_new (DIGEST_SHA1);
	if (digest == NULL) {
		g_free (str);
		return NULL;
	}

	digest_append (digest, str, strlen (str));
	hash = digest_final_str (digest);
	digest_free (digest);

	return hash;
}

char *
base64_encode (void *buf, unsigned int size)
{
	BIO *base64_bio = NULL;
	BIO *mem_bio = NULL;
	BIO *bio = NULL;
	char *encoded = NULL;
	int ret = 0;

	g_return_val_if_fail (buf != NULL, NULL);
	g_return_val_if_fail (size > 0, NULL);

	base64_bio = BIO_new (BIO_f_base64());
	mem_bio = BIO_new (BIO_s_mem());

	bio = BIO_push (base64_bio, mem_bio);

	ret = BIO_write (bio, buf, size);
	BIO_flush (bio);

	if (ret > 0) {
		char *tmp = NULL;
		ret = BIO_get_mem_data (bio, &tmp);
		encoded = g_strndup (tmp, ret - 1);
	}

	BIO_free_all (bio);

	return encoded;
}

void *
base64_decode (char *buf, size_t size)
{
	BIO *bio = NULL;
	BIO *base64_bio = NULL;
	BIO *mem_bio = NULL;
	char *decoded = NULL;
	unsigned int len = 0;

	mem_bio = BIO_new_mem_buf (buf, (int) size);
	base64_bio = BIO_new (BIO_f_base64());

	g_return_val_if_fail (buf != NULL, NULL);
	g_return_val_if_fail (size > 0, NULL);

	/* Check see see if newlines should be expected */
	if (strchr (buf, '\n') == NULL)
		BIO_set_flags (base64_bio, BIO_FLAGS_BASE64_NO_NL);

	bio = BIO_push (base64_bio, mem_bio);

	len = BIO_pending (bio);

	/* Note: it is assumed that the encoded data will always be */
	/* longer than unencoded data, is this correct? */
	if (len > 0) {
		char *tmp = NULL;
		int unencoded_len = 0;

		tmp = g_new (char, len);

		unencoded_len = BIO_read (bio, tmp, len);	
		tmp [unencoded_len] = '\0';

		decoded = g_strdup (tmp);

		g_free (tmp);
	}

	BIO_free_all(bio);

	return decoded;
}
